package othello.util;

import java.util.Observable;
import java.util.logging.Level;
import java.util.logging.Logger;

import othello.gui.Othello;
import othello.neuralnetwork.NeuralNetwork;
import othello.players.IPlayer;
import othello.players.learnplayers.AbstractNeuralNetworkPlayer;
import othello.players.learnplayers.TDPlayer;
import othello.players.learnplayers.AbstractNeuralNetworkPlayer.BenchmarkState;

public class GameDetails extends Observable{
    private final double nnlearningRate;
    private final boolean loadGames;
    private final boolean saveGames;
    private final boolean useEligibility;
    private final BenchmarkState benchmarkState;
    private IPlayer benchMarkPlayer;
    private IPlayer white;
    private IPlayer black;
    private final int maxGames;
    private final int maxGamesToLearn;
    private final int maxGamesToPlay;
    private int id;
    private int currentGame = 0;
    private final int run;
    private final boolean randFirstFourMoves;
    private int realMaxGames;

    public GameDetails(Class<? extends IPlayer> w, Class<? extends IPlayer> b, 
    		int maxGames, double nnlearningRate, boolean loadGames, boolean saveGames, 
    		boolean useEligibility, BenchmarkState benchmarkState, int maxGamesToLearn, int maxGamesToPlay,
                Class<? extends IPlayer> benchMarkPlayer, int run, boolean randFirstFourMoves) {
        try {
            createPlayers(w, b);
            createBenchmarkPlayer(benchMarkPlayer);
        } catch (Exception ex) {
            Logger.getLogger(Othello.class.getName()).log(Level.SEVERE, null, ex);
        }
        this.nnlearningRate = nnlearningRate;
        this.loadGames = loadGames;
        this.saveGames = saveGames;
        this.useEligibility = useEligibility;
        this.benchmarkState = benchmarkState;
        this.maxGames = maxGames + (1 + maxGames / maxGamesToLearn) * 2 * 236;
        this.maxGamesToLearn = maxGamesToLearn;
        this.maxGamesToPlay = 2 * 236;
        this.run = run;
        this.randFirstFourMoves = randFirstFourMoves;
        this.id = run;
        this.realMaxGames = maxGames;
        
    }
    
    private void createPlayers(Class<? extends IPlayer> w, Class<? extends IPlayer> b) throws Exception {
    	NeuralNetwork w_neuralNetwork = null;
		NeuralNetwork b_neuralNetwork = null;
    	
    	int w_outputNodes = (w == TDPlayer.class) ? 1 : 64;
    	int b_outputNodes = (b == TDPlayer.class) ? 1 : 64;
    	
    	// Use same NN if black is the same kind of player as white
    	// Otherwise, create a new NN
    	if (AbstractNeuralNetworkPlayer.class.isAssignableFrom(w)) {
    		w_neuralNetwork = new NeuralNetwork(50, w_outputNodes);
    		white = w.getConstructor(NeuralNetwork.class).newInstance(w_neuralNetwork);
    	} else {
    		white = w.newInstance();
    	}
    	
    	if (AbstractNeuralNetworkPlayer.class.isAssignableFrom(b)) {
    		//b_neuralNetwork = (w == b) ? w_neuralNetwork : new NeuralNetwork(50, b_outputNodes);
    		b_neuralNetwork = w_neuralNetwork;
    		black = b.getConstructor(NeuralNetwork.class).newInstance(b_neuralNetwork);
    	} else {
    		black = b.newInstance();
    	}
    	
    	if (w_neuralNetwork == b_neuralNetwork && w_neuralNetwork != null)
    		System.out.println("debug> Using same neural network for both players.");
    }
    
    private void createBenchmarkPlayer(Class<? extends IPlayer> benchMarkPlayer) throws Exception {
        if (AbstractNeuralNetworkPlayer.class.isAssignableFrom(benchMarkPlayer)) {
            NeuralNetwork neuralNetwork = new NeuralNetwork(50, (benchMarkPlayer == TDPlayer.class) ? 1 : 64);
            this.benchMarkPlayer = benchMarkPlayer.getConstructor(NeuralNetwork.class).newInstance(neuralNetwork);
        } else {
            this.benchMarkPlayer = benchMarkPlayer.newInstance();
        }
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getId(){
        return this.id;
    }

    public String getName() {
        StringBuilder result = new StringBuilder(white.getName());
        result.append(" ");
        result.append(black.getName());
        return result.toString();
    }

    @Override
    public String toString() {
    	// setChanged()
        StringBuilder result = new StringBuilder();
        result.append(this.currentGame);
        result.append(" / ");
        result.append(maxGames); // + (1 + realMaxGames / maxGamesToLearn) * maxGamesToPlay);

    	return result.toString();
    }
    
    public IPlayer getBlackPlayer() {
        return black;
    }

    public IPlayer getWhitePlayer() {
        return white;
    }
    
    public double getNNLearningRate() {
    	return nnlearningRate;
    }
    
    public boolean getLoadGames() {
    	return loadGames;
    }
    
    public boolean getSaveGames() {
    	return saveGames;
    }

    public boolean getUseEligibility() {
            return useEligibility;
    }

    public int getMaxGames() {
            return maxGames;
    }

    public int getMaxGamesToLearn() {
            return maxGamesToLearn;
    }

    public int getMaxGamesToPlay() {
            return maxGamesToPlay;
    }

    public BenchmarkState getBenchmarkState() {
            return benchmarkState;
    }

    public IPlayer getBenchmarkPlayer() {
        return this.benchMarkPlayer;
    }
    
    public boolean getRandFirstFourMoves() {
		return this.randFirstFourMoves;
	}
	
	public int getRealMaxGames() {
		return this.realMaxGames;
	}
	
    
    public void nextGame() {
    	this.currentGame++;
    	setChanged();
    	notifyObservers();
    }
    
    public int getRun() {
		return run;
	}
}
